using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using Autofac;
using Autofac.Features.ResolveAnything;
using Autofac.Integration.Mef;
using RandomSearch.Plugins.DefaultSearch;
using RandomSearch.PluginsCommon;

namespace RandomSearch.Host {
    public class RandomSearchServer : ISearchServer {
        private const string PluginDirectory = "Plugins";
        private readonly object _syncObject = new object();
        private IContainer _ctx;
        private volatile Exception _lastException;
        private List<SearchResult> _results = new List<SearchResult>();
        private volatile bool _searchInProgress;
        private IDisposable _subscription;

        static RandomSearchServer() {
            //preload common
            AppDomain.CurrentDomain.Load(typeof (ISearchPlugin).Assembly.FullName);
        }

        public RandomSearchServer() {
            _ctx = CreateContainer();
        }

        private IEnumerable<ISearchPlugin> Plugins {
            get { return _ctx.Resolve<IEnumerable<ISearchPlugin>>(); }
        }

        #region ISearchServer Members

        public void StartSearch(SearchOptions options) {
            Contract.Assert(null != options);
            Contract.Assert(!string.IsNullOrWhiteSpace(options.PathToRootFolder));

            if (_searchInProgress) throw new WrongStateException("Search in progress");

            _searchInProgress = true;
            IEnumerable<ISearchPlugin> plugins = Plugins;

            ISearchPlugin bs = plugins.Where(x => x.PluginId == options.PluginId).First();

            IObservable<SearchResult> results = bs.Search(options).ToObservable(new NewThreadScheduler());

            _subscription = results.Subscribe(x => _results.Add(x),
                                              onCompleted: () => { _searchInProgress = false; },
                                              onError: e =>
                                                           {
                                                               _lastException = e;
                                                               _searchInProgress = false;
                                                           });
        }

        public ReadyResultsResponse GetReadyResults() {
            if (!_searchInProgress && null != _lastException)
                return new ReadyResultsResponse {InProgress = false, Error = _lastException};
            if (!_searchInProgress && 0 == _results.Count)
                return new ReadyResultsResponse {InProgress = false};

            var tmp = new List<SearchResult>();
            List<SearchResult> res = Interlocked.Exchange(ref _results, tmp);
            return new ReadyResultsResponse {InProgress = true, Results = res};
        }

        public void CancelSearch() {
            if (_searchInProgress) {
                lock (_syncObject) {
                    if (_searchInProgress) {
                        _subscription.Dispose();
                        _searchInProgress = false;
                    }
                }
            }
        }

        public List<SearchPluginData> GetListOfPlugins() {
            List<SearchPluginData> res = null;
            var thread = new Thread(()=>
                                           {
                                               res = Plugins.Select(x => new SearchPluginData
                                               {
                                                   DisplayName = x.PluginName,
                                                   PluginId = x.PluginId,
                                                   SettingsXAML = x.Options
                                               }).ToList();                                   
                                           });
            thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA 
            thread.Start();
            thread.Join();
            return res;
        }

        public void RefreshPlugins() {
            _ctx = CreateContainer();
        }

        public void DoHeating() {
            //do nothing, just for heating up wcf and mef
        }

        #endregion

        private static IContainer CreateContainer() {
            var builder = new ContainerBuilder();
            string exPath = AppDomain.CurrentDomain.BaseDirectory;
            string dir = Path.Combine(exPath, PluginDirectory);
            var catalogPlugins = new DirectoryCatalog(dir);
            var catalogAssemblies = new AssemblyCatalog(typeof (DefaultSearch).Assembly);
            builder.RegisterComposablePartCatalog(catalogAssemblies);
            builder.RegisterComposablePartCatalog(catalogPlugins);
            builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());

            return builder.Build();
        }
    }
}